#ifndef __CS_IO_H
#define __CS_IO_H

// Standard Headers
#include<istream>

// Boost Headers
#include<boost/shared_array.hpp>

// CS Headers
#include<cs/types.h>
#include<cs/binary_traits.h>
#include<cs/byte_order.h>

#pragma region BINARY_UNICODE_CHARACTERS

inline char16 LE_read_char16(std::istream& is)
{
 char16 temp;
 is.read((char*)&temp, sizeof(temp));
 return temp;
}

inline char16 BE_read_char16(std::istream& is)
{
 uint16 temp;
 is.read((char*)&temp, sizeof(temp));
 reverse_byte_order(&temp);
 return static_cast<char16>(temp);
}

inline char32 LE_read_char32(std::istream& is)
{
 char32 temp;
 is.read((char*)&temp, sizeof(temp));
 return temp;
}

inline char32 BE_read_char32(std::istream& is)
{
 uint32 temp;
 is.read((char*)&temp, sizeof(temp));
 reverse_byte_order(&temp);
 return static_cast<char32>(temp);
}

inline char16 LE_read_char16(std::istream& is, uint64 offset)
{
 is.seekg(offset);
 if(is.fail()) return char16();
 return LE_read_char16(is);
}

inline char16 BE_read_char16(std::istream& is, uint64 offset)
{
 is.seekg(offset);
 if(is.fail()) return char16();
 return BE_read_char16(is);
}

inline char32 LE_read_char32(std::istream& is, uint64 offset)
{
 is.seekg(offset);
 if(is.fail()) return char32();
 return LE_read_char32(is);
}

inline char32 BE_read_char32(std::istream& is, uint64 offset)
{
 is.seekg(offset);
 if(is.fail()) return char32();
 return BE_read_char32(is);
}

inline void LE_write_char16(std::ostream& os, char16 value)
{
 os.write((char*)&value, sizeof(value));
}

inline void BE_write_char16(std::ostream& os, char16 value)
{
 reverse_byte_order(&value);
 os.write((char*)&value, sizeof(value));
}

inline void LE_write_char32(std::ostream& os, char32 value)
{
 os.write((char*)&value, sizeof(value));
}

inline void BE_write_char32(std::ostream& os, char32 value)
{
 reverse_byte_order(&value);
 os.write((char*)&value, sizeof(value));
}

#pragma endregion

#pragma region BINARY_08_BIT_CHARACTERS

inline schar08 LE_read_schar08(std::istream& is)
{
 schar08 temp;
 is.read((char*)&temp, sizeof(temp));
 return temp;
}

inline schar08 BE_read_schar08(std::istream& is)
{
 schar08 temp;
 is.read((char*)&temp, sizeof(temp));
 return temp;
}

inline uchar08 LE_read_uchar08(std::istream& is)
{
 uchar08 temp;
 is.read((char*)&temp, sizeof(temp));
 return temp;
}

inline uchar08 BE_read_uchar08(std::istream& is)
{
 uchar08 temp;
 is.read((char*)&temp, sizeof(temp));
 return temp;
}

inline schar08 LE_read_schar08(std::istream& is, uint64 offset)
{
 is.seekg(offset);
 if(is.fail()) return schar08();
 return LE_read_schar08(is);
}

inline schar08 BE_read_schar08(std::istream& is, uint64 offset)
{
 is.seekg(offset);
 if(is.fail()) return schar08();
 return BE_read_schar08(is);
}

inline uchar08 LE_read_uchar08(std::istream& is, uint64 offset)
{
 is.seekg(offset);
 if(is.fail()) return uchar08();
 return LE_read_uchar08(is);
}

inline uchar08 BE_read_uchar08(std::istream& is, uint64 offset)
{
 is.seekg(offset);
 if(is.fail()) return uchar08();
 return BE_read_uchar08(is);
}

inline void LE_write_schar08(std::ostream& os, schar08 value)
{
 os.write((char*)&value, sizeof(value));
}

inline void BE_write_schar08(std::ostream& os, schar08 value)
{
 os.write((char*)&value, sizeof(value));
}

inline void LE_write_uchar08(std::ostream& os, uchar08 value)
{
 os.write((char*)&value, sizeof(value));
}

inline void BE_write_uchar08(std::ostream& os, uchar08 value)
{
 os.write((char*)&value, sizeof(value));
}

#pragma endregion

#pragma region BINARY_08_BIT_INTEGERS

inline sint08 LE_read_sint08(std::istream& is)
{
 sint08 temp;
 is.read((char*)&temp, sizeof(temp));
 return temp;
}

inline sint08 BE_read_sint08(std::istream& is)
{
 sint08 temp;
 is.read((char*)&temp, sizeof(temp));
 return temp;
}

inline uint08 LE_read_uint08(std::istream& is)
{
 uint08 temp;
 is.read((char*)&temp, sizeof(temp));
 return temp;
}

inline uint08 BE_read_uint08(std::istream& is)
{
 uint08 temp;
 is.read((char*)&temp, sizeof(temp));
 return temp;
}

inline sint08 LE_read_sint08(std::istream& is, uint64 offset)
{
 is.seekg(offset);
 if(is.fail()) return sint08();
 return LE_read_sint08(is);
}

inline sint08 BE_read_sint08(std::istream& is, uint64 offset)
{
 is.seekg(offset);
 if(is.fail()) return sint08();
 return BE_read_sint08(is);
}

inline uint08 LE_read_uint08(std::istream& is, uint64 offset)
{
 is.seekg(offset);
 if(is.fail()) return uint08();
 return LE_read_uint08(is);
}

inline uint08 BE_read_uint08(std::istream& is, uint64 offset)
{
 is.seekg(offset);
 if(is.fail()) return uint08();
 return BE_read_uint08(is);
}

inline void LE_write_sint08(std::ostream& os, sint08 value)
{
 os.write((char*)&value, sizeof(value));
}

inline void BE_write_sint08(std::ostream& os, sint08 value)
{
 os.write((char*)&value, sizeof(value));
}

inline void LE_write_uint08(std::ostream& os, uint08 value)
{
 os.write((char*)&value, sizeof(value));
}

inline void BE_write_uint08(std::ostream& os, uint08 value)
{
 os.write((char*)&value, sizeof(value));
}

#pragma endregion

#pragma region BINARY_16_BIT_INTEGERS

inline sint16 LE_read_sint16(std::istream& is)
{
 sint16 temp;
 is.read((char*)&temp, sizeof(temp));
 return temp;
}

inline sint16 BE_read_sint16(std::istream& is)
{
 sint16 temp;
 is.read((char*)&temp, sizeof(temp));
 reverse_byte_order(&temp);
 return temp;
}

inline uint16 LE_read_uint16(std::istream& is)
{
 uint16 temp;
 is.read((char*)&temp, sizeof(temp));
 return temp;
}

inline uint16 BE_read_uint16(std::istream& is)
{
 uint16 temp;
 is.read((char*)&temp, sizeof(temp));
 reverse_byte_order(&temp);
 return temp;
}

inline sint16 LE_read_sint16(std::istream& is, uint64 offset)
{
 is.seekg(offset);
 if(is.fail()) return sint16();
 return LE_read_sint16(is);
}

inline sint16 BE_read_sint16(std::istream& is, uint64 offset)
{
 is.seekg(offset);
 if(is.fail()) return sint16();
 return BE_read_sint16(is);
}

inline uint16 LE_read_uint16(std::istream& is, uint64 offset)
{
 is.seekg(offset);
 if(is.fail()) return uint16();
 return LE_read_uint16(is);
}

inline uint16 BE_read_uint16(std::istream& is, uint64 offset)
{
 is.seekg(offset);
 if(is.fail()) return uint16();
 return BE_read_uint16(is);
}

inline void LE_write_sint16(std::ostream& os, sint16 value)
{
 os.write((char*)&value, sizeof(value));
}

inline void BE_write_sint16(std::ostream& os, sint16 value)
{
 reverse_byte_order(&value);
 os.write((char*)&value, sizeof(value));
}

inline void LE_write_uint16(std::ostream& os, uint16 value)
{
 os.write((char*)&value, sizeof(value));
}

inline void BE_write_uint16(std::ostream& os, uint16 value)
{
 reverse_byte_order(&value);
 os.write((char*)&value, sizeof(value));
}

#pragma endregion

#pragma region BINARY_32_BIT_INTEGERS

inline sint32 LE_read_sint32(std::istream& is)
{
 sint32 temp;
 is.read((char*)&temp, sizeof(temp));
 return temp;
}

inline sint32 BE_read_sint32(std::istream& is)
{
 sint32 temp;
 is.read((char*)&temp, sizeof(temp));
 reverse_byte_order(&temp);
 return temp;
}

inline uint32 LE_read_uint32(std::istream& is)
{
 uint32 temp;
 is.read((char*)&temp, sizeof(temp));
 return temp;
}

inline uint32 BE_read_uint32(std::istream& is)
{
 uint32 temp;
 is.read((char*)&temp, sizeof(temp));
 reverse_byte_order(&temp);
 return temp;
}

inline sint32 LE_read_sint32(std::istream& is, uint64 offset)
{
 is.seekg(offset);
 if(is.fail()) return sint32();
 return LE_read_sint32(is);
}

inline sint32 BE_read_sint32(std::istream& is, uint64 offset)
{
 is.seekg(offset);
 if(is.fail()) return sint32();
 return BE_read_sint32(is);
}

inline uint32 LE_read_uint32(std::istream& is, uint64 offset)
{
 is.seekg(offset);
 if(is.fail()) return uint32();
 return LE_read_uint32(is);
}

inline uint32 BE_read_uint32(std::istream& is, uint64 offset)
{
 is.seekg(offset);
 if(is.fail()) return uint32();
 return BE_read_uint32(is);
}

inline void LE_write_sint32(std::ostream& os, sint32 value)
{
 os.write((char*)&value, sizeof(value));
}

inline void BE_write_sint32(std::ostream& os, sint32 value)
{
 reverse_byte_order(&value);
 os.write((char*)&value, sizeof(value));
}

inline void LE_write_uint32(std::ostream& os, uint32 value)
{
 os.write((char*)&value, sizeof(value));
}

inline void BE_write_uint32(std::ostream& os, uint32 value)
{
 reverse_byte_order(&value);
 os.write((char*)&value, sizeof(value));
}

#pragma endregion

#pragma region BINARY_64_BIT_INTEGERS

inline sint64 LE_read_sint64(std::istream& is)
{
 sint64 temp;
 is.read((char*)&temp, sizeof(temp));
 return temp;
}

inline sint64 BE_read_sint64(std::istream& is)
{
 sint64 temp;
 is.read((char*)&temp, sizeof(temp));
 reverse_byte_order(&temp);
 return temp;
}

inline uint64 LE_read_uint64(std::istream& is)
{
 uint64 temp;
 is.read((char*)&temp, sizeof(temp));
 return temp;
}

inline uint64 BE_read_uint64(std::istream& is)
{
 uint64 temp;
 is.read((char*)&temp, sizeof(temp));
 reverse_byte_order(&temp);
 return temp;
}

inline sint64 LE_read_sint64(std::istream& is, uint64 offset)
{
 is.seekg(offset);
 if(is.fail()) return sint64();
 return LE_read_sint64(is);
}

inline sint64 BE_read_sint64(std::istream& is, uint64 offset)
{
 is.seekg(offset);
 if(is.fail()) return sint64();
 return BE_read_sint64(is);
}

inline uint64 LE_read_uint64(std::istream& is, uint64 offset)
{
 is.seekg(offset);
 if(is.fail()) return uint64();
 return LE_read_uint64(is);
}

inline uint64 BE_read_uint64(std::istream& is, uint64 offset)
{
 is.seekg(offset);
 if(is.fail()) return uint64();
 return BE_read_uint64(is);
}

inline void LE_write_sint64(std::ostream& os, sint64 value)
{
 os.write((char*)&value, sizeof(value));
}

inline void BE_write_sint64(std::ostream& os, sint64 value)
{
 reverse_byte_order(&value);
 os.write((char*)&value, sizeof(value));
}

inline void LE_write_uint64(std::ostream& os, uint64 value)
{
 os.write((char*)&value, sizeof(value));
}

inline void BE_write_uint64(std::ostream& os, uint64 value)
{
 reverse_byte_order(&value);
 os.write((char*)&value, sizeof(value));
}

#pragma endregion

#pragma region BINARY_IEEE_754

inline binary32 LE_read_real16(std::istream& is)
{
 uint16 temp;
 is.read((char*)&temp, sizeof(temp));
 return binary_traits<binary32>::reinterpret_binary16(temp);
}

inline binary32 BE_read_real16(std::istream& is)
{
 uint16 temp = LE_read_uint16(is);
 reverse_byte_order(&temp);
 return binary_traits<binary32>::reinterpret_binary16(temp);
}

inline binary32 LE_read_real32(std::istream& is)
{
 binary32 temp;
 is.read((char*)&temp, sizeof(temp));
 return temp;
}

inline binary32 BE_read_real32(std::istream& is)
{
 uint32 temp = LE_read_uint32(is);
 reverse_byte_order(&temp);
 return binary_traits<binary32>::reinterpret(temp);
}

inline binary64 LE_read_real64(std::istream& is)
{
 binary64 temp;
 is.read((char*)&temp, sizeof(temp));
 return temp;
}

inline binary64 BE_read_real64(std::istream& is)
{
 uint64 temp = LE_read_uint64(is);
 reverse_byte_order(&temp);
 return binary_traits<binary64>::reinterpret(temp);
}

inline binary32 LE_read_real16(std::istream& is, uint64 offset)
{
 is.seekg(offset);
 if(is.fail()) return binary32();
 return LE_read_real16(is);
}

inline binary32 BE_read_real16(std::istream& is, uint64 offset)
{
 is.seekg(offset);
 if(is.fail()) return binary32();
 return BE_read_real16(is);
}

inline binary32 LE_read_real32(std::istream& is, uint64 offset)
{
 is.seekg(offset);
 if(is.fail()) return binary32();
 return LE_read_real32(is);
}

inline binary32 BE_read_real32(std::istream& is, uint64 offset)
{
 is.seekg(offset);
 if(is.fail()) return binary32();
 return BE_read_real32(is);
}

inline binary64 LE_read_real64(std::istream& is, uint64 offset)
{
 is.seekg(offset);
 if(is.fail()) return binary64();
 return LE_read_real64(is);
}

inline binary64 BE_read_real64(std::istream& is, uint64 offset)
{
 is.seekg(offset);
 if(is.fail()) return binary64();
 return BE_read_real64(is);
}

inline void LE_write_real32(std::ostream& os, binary32 value)
{
 os.write((char*)&value, sizeof(value));
}

inline void BE_write_real32(std::ostream& os, binary32 value)
{
 uint32* ptr = reinterpret_cast<uint32*>(&value);
 reverse_byte_order(ptr);
 os.write((char*)ptr, sizeof(uint32));
}

inline void LE_write_real64(std::ostream& os, binary64 value)
{
 os.write((char*)&value, sizeof(value));
}

inline void BE_write_real64(std::ostream& os, binary64 value)
{
 uint64* ptr = reinterpret_cast<uint64*>(&value);
 reverse_byte_order(ptr);
 os.write((char*)ptr, sizeof(uint64));
}

#pragma endregion

#pragma region BINARY_ARRAYS

template<class T>
inline bool LE_read_array(std::istream& is, T* data, size_t n)
{
 is.read((char*)&data[0], n*sizeof(T));
 if(is.fail()) return false;
 return true;
}

template<class T>
inline bool BE_read_array(std::istream& is, T* data, size_t n)
{
 is.read((char*)&data[0], n*sizeof(T));
 if(is.fail()) return false;
 reverse_byte_order(data, n);
 return true;
}

template<>
inline bool BE_read_array(std::istream& is, binary32* data, size_t n)
{
 uint32* temp = reinterpret_cast<uint32*>(data);
 is.read((char*)&temp[0], n*sizeof(uint32));
 if(is.fail()) return false;
 reverse_byte_order(temp, n);
 return true;
}

template<>
inline bool BE_read_array(std::istream& is, binary64* data, size_t n)
{
 uint64* temp = reinterpret_cast<uint64*>(data);
 is.read((char*)&temp[0], n*sizeof(uint64));
 if(is.fail()) return false;
 reverse_byte_order(temp, n);
 return true;
}

template<class T>
inline bool LE_read_array(std::istream& is, uint64 offset, T* data, size_t n)
{
 is.seekg(offset);
 if(is.fail()) return false;
 return LE_read_array(ifile, data, n);
}

template<class T>
inline bool BE_read_array(std::istream& is, uint64 offset, T* data, size_t n)
{
 is.seekg(offset);
 if(is.fail()) return false;
 return BE_read_array(ifile, data, n);
}

template<class T>
inline bool LE_write_array(std::ostream& os, const T* data, size_t n)
{
 // save data
 os.write((char*)&data[0], n*sizeof(T));
 if(os.fail()) return false;
 return true;
}

template<class T>
inline bool BE_write_array(std::ostream& os, const T* data, size_t n)
{
 // copy and reverse data
 boost::shared_array<T> copy(new T[n]);
 for(size_t i = 0; i < n; i++) copy[i] = data[i];
 reverse_byte_order(copy.get(), n);
 // save data
 os.write((char*)copy.get(), n*sizeof(T));
 if(os.fail()) return false;
 return true;
}

template<>
inline bool BE_write_array(std::ostream& os, const binary32* data, size_t n)
{
 // copy and reverse data as uint32 to prevent NaN conversion
 const uint32* temp = reinterpret_cast<const uint32*>(data);
 boost::shared_array<uint32> copy(new uint32[n]);
 for(size_t i = 0; i < n; i++) copy[i] = temp[i];
 reverse_byte_order(copy.get(), n);
 // save data
 os.write((char*)copy.get(), n*sizeof(binary32));
 if(os.fail()) return false;
 return true;
}

template<>
inline bool BE_write_array(std::ostream& os, const binary64* data, size_t n)
{
 // copy and reverse data as uint64 to prevent NaN conversion
 const uint64* temp = reinterpret_cast<const uint64*>(data);
 boost::shared_array<uint64> copy(new uint64[n]);
 for(size_t i = 0; i < n; i++) copy[i] = temp[i];
 reverse_byte_order(copy.get(), n);
 // save data
 os.write((char*)copy.get(), n*sizeof(binary64));
 if(os.fail()) return false;
 return true;
}

#pragma endregion

#pragma region BINARY_STRINGS

template<class T>
inline size_t LE_read_string(std::istream& is, T* data, size_t size, T delimiter = static_cast<T>(0))
{
 // nothing to do
 if(data == nullptr) return 0;
 if(size == 0) return 0;

 // for each
 for(size_t curr = 0; curr < size; curr++)
    {
     // read character
     T item;
     is.read((char*)&item, sizeof(item));

     // on failure, assume we read delimiter
     if(is.fail() || is.eof()) {
        data[curr] = delimiter;
        return (curr + 1);
       }

     // set character 
     data[curr] = item;
     if(item == delimiter) return (curr + 1);
    }

 // always terminate with delimiter
 data[size - 1] = delimiter;
 return size;
}

template<class T>
inline size_t BE_read_string(std::istream& is, T* data, size_t size, T delimiter = static_cast<T>(0))
{
 // nothing to do
 if(data == nullptr) return 0;
 if(size == 0) return 0;

 // for each
 for(size_t curr = 0; curr < size; curr++)
    {
     // read character
     T item;
     is.read((char*)&item, sizeof(item));

     // on failure, assume we read delimiter
     if(is.fail() || is.eof()) {
        data[curr] = delimiter;
        return (curr + 1);
       }

     // set character
     reverse_byte_order(&item);
     data[curr] = item;
     if(item == delimiter) return (curr + 1);
    }

 // always terminate with delimiter
 data[size - 1] = delimiter;
 return size;
}

// specialize so byte-order reversal is unnecessary
template<>
inline size_t BE_read_string(std::istream& is, schar08* data, size_t size, schar08 delimiter)
{
 return LE_read_string(is, data, size, delimiter);
}

// specialize so byte-order reversal is unnecessary
template<>
inline size_t BE_read_string(std::istream& is, uchar08* data, size_t size, uchar08 delimiter)
{
 return LE_read_string(is, data, size, delimiter);
}

template<class T>
inline bool LE_write_string(std::ofstream& os, const T* data)
{
 if(!str) return false;
 uint32 size = (std::char_traits<T>::length(data) + 1)*sizeof(T);
 os.write(reinterpret_cast<const char*>(data), size);
 return !os.fail();
}

template<class T>
inline bool BE_write_string(std::ofstream& os, const T* data)
{
 // copy data
 if(!str) return false;
 uint32 elem = std::char_traits<T>::length(data) + 1;
 boost::shared_array<T> copy(new T[elem]);
 std::copy(data, data + elem, copy.get());
 // save reversed copy of data
 reverse_byte_order(copy.get(), elem);
 os.write(reinterpret_cast<const char*>(copy), elem*sizeof(T));
 return !os.fail();
}

#pragma endregion

#endif
